package com.domainlanguage.time;

import static org.hibernate.Hibernate.BOOLEAN;
import org.hibernate.HibernateException;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.type.Type;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;

import static com.domainlanguage.TimeAndMoneyTypes.*;

/**
 * Hibernate user type for {@link TimeInterval}.
 *
 * Start and end timepoints are backed by {@link java.sql.Types.TIMESTAMP},
 * closedness are backed by {@link java.sql.Types.BOOLEAN}.
 *
 */
public class BigIntTimeIntervalType extends ImmutableCompositeUserType {

    private static final String[] PROPERRTY_NAMES = {"start", "closedStart", "end", "closedEnd"};
    private static final Type[] PROPERTY_TYPES = {BIG_INT_TIME_POINT, BOOLEAN, BIG_INT_TIME_POINT, BOOLEAN};

    @Override
    public String[] getPropertyNames() {
        return PROPERRTY_NAMES;
    }

    @Override
    public Type[] getPropertyTypes() {
        return PROPERTY_TYPES;
    }

    @Override
    public Object getPropertyValue(final Object component, final int property) throws HibernateException {
        final TimeInterval timeInterval = (TimeInterval) component;
        switch (property) {
            case 0:
                return timeInterval.lowerLimit();
            case 1:
                return timeInterval.includesLowerLimit();
            case 2:
                return timeInterval.upperLimit();
            case 3:
                return timeInterval.includesUpperLimit();
            default:
                throw new HibernateException(
                    "Property index " + property + " is out of range. Properties are: " +
                    Arrays.toString(getPropertyNames())
                );
        }
    }

    @Override
    public Class returnedClass() {
        return TimeInterval.class;
    }

    @Override
    public Object nullSafeGet(final ResultSet rs, final String[] names, final SessionImplementor session, Object owner) throws HibernateException, SQLException {
        final TimePoint start = (TimePoint) BIG_INT_TIME_POINT.nullSafeGet(rs, names[0]);
        final Boolean closedStart = (Boolean) BOOLEAN.nullSafeGet(rs, names[1]);
        final TimePoint end = (TimePoint) BIG_INT_TIME_POINT.nullSafeGet(rs, names[2]);
        final Boolean closedEnd = (Boolean) BOOLEAN.nullSafeGet(rs, names[3]);

        if (start == null || closedStart == null || end == null || closedEnd == null) {
            return null;
        } else {
            return TimeInterval.over(start, closedStart, end, closedEnd);
        }
    }

    @Override
    public void nullSafeSet(final PreparedStatement st, final Object value, final int index, final SessionImplementor session) throws HibernateException, SQLException {
        if (value == null) {
            st.setNull(index, BIG_INT_TIME_POINT.sqlType());
            st.setNull(index + 1, BOOLEAN.sqlType());
            st.setNull(index + 2, BIG_INT_TIME_POINT.sqlType());
            st.setNull(index + 3, BOOLEAN.sqlType());
            return;
        }

        final TimeInterval timeInterval = (TimeInterval) value;
        BIG_INT_TIME_POINT.nullSafeSet(st, timeInterval.start(), index);
        BOOLEAN.nullSafeSet(st, timeInterval.includesLowerLimit(), index + 1);
        BIG_INT_TIME_POINT.nullSafeSet(st, timeInterval.end(), index + 2);
        BOOLEAN.nullSafeSet(st, timeInterval.includesUpperLimit(), index + 3);
    }
}